package coordinator;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import monitor.Monitor;

import org.omg.CosNaming.NamingContextExt;
import org.omg.PortableServer.POA;

import euclid.*;
import euclid.CoordinatorPackage.exInvalidParameter;
import euclid.CoordinatorPackage.exNoStarter;

/**
 * Implementation of the methods for the coordinator
 * @author Laurine
 *
 */
public class CoordinatorImpl extends CoordinatorPOA {
	@SuppressWarnings("unused")
	private String name;
	@SuppressWarnings("unused")
	private POA rootPoa;
	@SuppressWarnings("unused")
	private euclid.Client client;
	private Monitor monitor;
	@SuppressWarnings("unused")
	private int minNumberProcess;
	@SuppressWarnings("unused")
	private int maxNumberProcess;
	private int minDelay;
	private int maxDelay;
	private int timeout;
	private int gcd;
	private NamingContextExt nc;
	private static ArrayList<Starter> listStarters = new ArrayList<Starter>();
	private static ArrayList<gcdProcess> listProcess = new ArrayList<gcdProcess>();
	private int nbProcess;
	
	//to sort a hashmap and be able to find the 3 processes with the smallest values
	Comparator<Integer> valueComparator = new Comparator<Integer>() {
		@Override
		public int compare(Integer arg0, Integer arg1) {
			return arg0.compareTo(arg1);
			}
		};
	
	/**
	 * Constructor of CoordinatorImpl
	 * @param name of the coordinator
	 * @param rootPoa
	 * @param nc
	 */
	public CoordinatorImpl(String name, POA rootPoa, NamingContextExt nc)
	{
		this.name = name;
		this.rootPoa = rootPoa;
		this.nc = nc;
		nbProcess=0;
	}


	@Override
	public Starter[] getStarterList() {
		System.out.println(listStarters.size());
		return listStarters.toArray(new Starter[listStarters.size()]);
	}


	@Override
	public void endCalculation(int result) {
		//end all the starters
		for(Starter s:listStarters)
		{
			s.endStarter();
		}
		nbProcess=0;
		listProcess=new ArrayList<gcdProcess>();
		if(result!=0)
			System.out.println("End of the calculation. Result= " + result);
		
	}


	@Override
	public void logClient(Client client) {
		this.client = client;		
	}


	@Override
	public void startGcdCalculation(int minNumberProcess, int maxNumberProcess,
			int minDelay, int maxDelay, int timeout, int gcd) throws exInvalidParameter, exNoStarter {
		this.minNumberProcess = minNumberProcess;
		this.maxNumberProcess = maxNumberProcess;
		this.minDelay = minDelay;
		this.maxDelay = maxDelay;
		this.timeout = timeout;
		this.gcd = gcd;
		
		System.out.println("Coordinator received a calculation request");
		
		if(listStarters.size() == 0)
		{
			throw new exNoStarter("No Starter was found. Calculation impossible");
		}
		//First: check if the parameters are good
		if(minDelay < 0 || maxDelay<0 || minDelay>maxDelay)
			throw new exInvalidParameter("Please verify the delay time interval. The two numbers must be higher than 0" +
					" and the max must be higher than the min");
		else if(minNumberProcess<0 || maxNumberProcess < 0 || minNumberProcess>=maxNumberProcess)
			throw new exInvalidParameter("Please verify the number of processes. The two numbers must be higher than 0" +
					" and the max must be higher and different than the min");
		else if(timeout<0)
			throw new exInvalidParameter("Timeout must be higher than 0");
		else if(gcd<0)
			throw new exInvalidParameter("the GCD must be higher than 0");
		
		//generate a random number of processes to start for each starter
		Random r = new Random();
		int[] random = new int[listStarters.size()];
		for(int i=0; i<listStarters.size(); i++)
		{
			random[i] = minNumberProcess + r.nextInt(maxNumberProcess - minNumberProcess);
			//to know how many process were started
			nbProcess+=random[i];
		}
		
		//start all of this processes
		for(int i=0; i<listStarters.size(); i++)
		{
			listStarters.get(i).startProcess(random[i]);
		}
	}


	@Override
	public void logStarter(Starter starter) {
		try{
			//add the starter to the list
			listStarters.add(starter);
		}catch(Exception e){
			System.err.println("Problem when logging the starter");
		}
		
	}


	@Override
	public void logProcess(String starter, int id) {
		Starter s;
		Hashtable<gcdProcess, Integer> valuesProcesses = new Hashtable<gcdProcess, Integer>(); 
		Random r = new Random();
		
		try {
			s = StarterHelper.narrow(nc.resolve_str(starter));
			listProcess.add(s.getProcess(id));
			
			//if all the process were logged, we can start the calculation
			if(listProcess.size() == nbProcess)
			{
				System.out.println("The calculation can start");
				//start the calculation
				//1. build the ring with random order
				ArrayList<Integer> orderProcess = new ArrayList<Integer>();
				orderProcess = giveRandomOrder();
				//2. give a start value, a delay time, a timeout, the left and right neighbors to all the process
				for(int i=0; i<listProcess.size();i++)
				{
					//the start value
					int startVal = gcd * (r.nextInt(100)+1) * (r.nextInt(100)+1);
					valuesProcesses.put(listProcess.get(i), startVal);
					//the delay time
					int delay = minDelay + r.nextInt(maxDelay - minDelay);
					//the left neighbor
					int left = orderProcess.get(((i-1) + listProcess.size())%listProcess.size());
					//the right neighbor
					int right = orderProcess.get((i+1)%listProcess.size());
					listProcess.get(orderProcess.get(i)).init(startVal, delay, timeout, 
							listProcess.get(left), listProcess.get(right),monitor);
					
				}
				//Display the start values on the monitor
				int[] toDisplay = new int[valuesProcesses.size()];
				int j=-1;
				for(int i: valuesProcesses.values())
				{
					j++;
					toDisplay[j] = i;
				}
				try{
					monitor.startzahlen(toDisplay);
				}catch(Exception e){
				}
				
				//3. Chose the 3 processes with the smallest values to begin, and give them a value Mi
				//sort startValues to know which are the 3 smallest.
				/*to sort by values*/
				MapValueComparator<gcdProcess, Integer> mapComparator = 
						new MapValueComparator<gcdProcess, Integer>(valuesProcesses, valueComparator);
				Map<gcdProcess, Integer> sortedOnValuesMap = new TreeMap<gcdProcess, Integer>(mapComparator);
				sortedOnValuesMap.putAll(valuesProcesses);
				/* --- */
				int i=0;
				for(gcdProcess p: sortedOnValuesMap.keySet())
				{
					i++;
					if(i<=3)
					{
						//give the process a new value
						int m = gcd * (r.nextInt(100)+1) * (r.nextInt(100)+1);
						p.giveNewValue(m);
						System.out.println("The value " + m + " was given to the process " + p.getName()+"-"+p.getId());
					}
					else
						break;
				}
				//start the processes
				for(gcdProcess p: sortedOnValuesMap.keySet()){
					p.startProcess();
				}
				
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}


	/**
	 * to order all the process randomly
	 * @return an array with all the gcdProcess in a random order
	 */
	private ArrayList<Integer> giveRandomOrder() {
		Random r = new Random();
		ArrayList<Integer> number = new ArrayList<Integer>();
		ArrayList<Integer> randomOrder = new ArrayList<Integer>();
		for(int i=0; i<listProcess.size();i++){
			number.add(i);}
		
		int j;
		for(int i=0; i<listProcess.size(); i++)
		{
			j= r.nextInt(number.size());
			randomOrder.add(number.get(j));
			number.remove(j);
		}
		
		//display on monitor
		String[] toDisplay = new String[randomOrder.size()];
		for(int i=0; i<randomOrder.size(); i++)
		{
			toDisplay[i] = listProcess.get(randomOrder.get(i)).getProcessId();
		}
		try{
			monitor.ring(toDisplay);
		}catch(Exception e){}
		
		return randomOrder;
	}
	
	/**
	 * to sort a map according to the values
	 *
	 * @param <K>
	 * @param <V>
	 */
	public class MapValueComparator<K, V> implements Comparator<K>{

		  private final Map<K, V> mapToSort;
		  private final Comparator<V> valueComparator;

		  public MapValueComparator(Map<K, V> mapToSort, Comparator<V> valueComparator){
		     this.mapToSort = mapToSort;
		  this.valueComparator = valueComparator;
		  }

		   @Override
		  public int compare(K key1, K key2) {
		  return valueComparator.compare(mapToSort.get(key1), mapToSort.get(key2));
		}
	}

	@Override
	public void logMonitor(Monitor monitor) {
		this.monitor=monitor;
	}


	@Override
	public void endSystem() {
		for(Starter s: listStarters)
		{
			s.endStarter();
		}
		endCalculation(0);
		
	} 

}

